'use client';

import {
  type ChangeEvent,
  type FormEvent,
  useEffect,
  useRef,
  useState,
} from 'react';
import type { IDifference } from '@/interfaces';
import {
  createUserByEmail,
  createUserByPhone,
  createUserByUsername,
  queryAccountPublicKey,
  queryPasswordPublicKey,
} from '@/services/api';
import { useMutation, useQuery } from '@tanstack/react-query';
import { nanoid } from 'nanoid';
import type JSEncrypt from 'jsencrypt';
import diff from 'microdiff';
import useToast from '@/hooks/useToast';
import { clearFormData, getDiffData, isPhone } from '@/lib/tool';
import Box from '@/app/[locale]/admin/common/box';
import Spinner from '@/app/[locale]/component/spinner/spinner';
import type { PrefixedTTranslatedFields } from '@/lib/dictionaries';

export default function CreateUserAdminPage(
  this: any,
  {
    source,
    translatedFields,
  }: {
    source?: any;
    translatedFields: PrefixedTTranslatedFields<'userAdminPage'>;
  },
) {
  const jsEncryptRef = useRef<JSEncrypt | null>(null);

  async function getJsEncrypt(): Promise<JSEncrypt> {
    let jsEncrypt;
    if (jsEncryptRef.current) {
      jsEncrypt = jsEncryptRef.current;
    } else {
      const JSEncrypt = (await import('jsencrypt')).JSEncrypt;
      jsEncrypt = new JSEncrypt();
      jsEncryptRef.current = jsEncrypt;
    }
    return jsEncrypt;
  }

  return (
    <Box>
      <div className="vstack gap-4">
        <Username
          getJsEncrypt={getJsEncrypt}
          translatedFields={translatedFields}
        />
        <Phone
          getJsEncrypt={getJsEncrypt}
          translatedFields={translatedFields}
        />
        <Email
          getJsEncrypt={getJsEncrypt}
          translatedFields={translatedFields}
        />
      </div>
    </Box>
  );
}

const Username = ({
  getJsEncrypt,
  translatedFields,
}: {
  getJsEncrypt: () => Promise<JSEncrypt>;
  translatedFields: PrefixedTTranslatedFields<'userAdminPage'>;
}) => {
  const [differenceData, setDifferenceData] = useState<IDifference[]>([]);
  const { show } = useToast();
  const [form, setForm] = useState({
    username: '',
    password: '',
  });
  const [randomPassword, setRandomPassword] = useState('');
  const [isDisabledSave, setIsDisabledSave] = useState(true);

  const createUserByUsernameMutation = useMutation(createUserByUsername);

  const queryPasswordPublicKeyQuery = useQuery(
    ['/key', '/public', '/password'],
    async (context) => {
      return (await queryPasswordPublicKey()) as string;
    },
    {
      enabled: false,
    },
  );

  useEffect(() => {
    const diffData = diff(
      {
        username: '',
        password: '',
      },
      form,
    );
    setDifferenceData(diffData);
    setIsDisabledSave(diffData.length === 0);
  }, [form]);

  async function onSubmit(e: FormEvent<HTMLFormElement>) {
    try {
      e.preventDefault();
      e.stopPropagation();

      checkForm();
      const data = getDiffData(differenceData) as any;
      const encryptedPassword = await getEncryptedPassword(data.password);
      await createUserByUsernameMutation.mutateAsync({
        data: {
          ...data,
          alias: data.username,
          password: encryptedPassword,
        },
      });

      show({
        type: 'SUCCESS',
        message: translatedFields.operate.saveCompleted,
      });
    } catch (e) {
      createUserByUsernameMutation.reset();
      show({
        type: 'DANGER',
        message: e,
      });
    } finally {
      setForm(clearFormData(form));
      setDifferenceData([]);
    }
  }

  async function getEncryptedPassword(password: string): Promise<string> {
    const jsEncrypt = await getJsEncrypt();
    const result = await queryPasswordPublicKeyQuery.refetch({
      throwOnError: true,
    });
    const publicKey = result.data;
    if (!publicKey) {
      throw translatedFields.tip.encryptDataFailed;
    }

    jsEncrypt.setPublicKey(publicKey);
    const encryptedData = jsEncrypt.encrypt(password);
    if (!encryptedData) {
      throw translatedFields.tip.encryptDataFailed;
    }

    return encryptedData;
  }

  function onClickGenerateRandomPassword() {
    const rPassword = nanoid();
    const password = rPassword.substring(0, Math.floor(rPassword.length / 2));
    setRandomPassword(password);
    setForm({ ...form, password });
  }

  function checkForm() {
    const { username, password } = form;

    if (!username) {
      throw translatedFields.tip.usernameRequired;
    }

    if (!password) {
      throw translatedFields.tip.passwordRequired;
    }
  }

  function onChange(e: ChangeEvent<HTMLInputElement>) {
    const name = e.target.name;
    const value = e.target.value;
    setForm({ ...form, [name]: value });
  }

  return (
    <div className="card">
      <div className="card-header bg-transparent text-secondary">
        {translatedFields.properties.useUsername}
      </div>
      <div className="card-body vstack gap-4">
        <form onSubmit={onSubmit} className="vstack gap-4">
          <div>
            <label className="form-label">
              {translatedFields.properties.username}
            </label>
            <input
              type="text"
              name="username"
              value={form.username}
              onChange={onChange}
              className="form-control"
              aria-describedby="username"
            />
          </div>

          <div>
            <label className="form-label">
              <span className="user-select-none">
                {translatedFields.properties.password}
              </span>
              {randomPassword && (
                <span className="mx-2 text-secondary">({randomPassword})</span>
              )}
            </label>
            <div className="input-group">
              <input
                type="password"
                name="password"
                value={form.password}
                onChange={onChange}
                className="form-control"
                autoComplete="password"
              />
              <button
                onClick={onClickGenerateRandomPassword}
                className="btn btn-outline-secondary"
                type="button"
              >
                {translatedFields.properties.randomPassword}
              </button>
            </div>
          </div>

          <button
            type="submit"
            disabled={createUserByUsernameMutation.isLoading || isDisabledSave}
            className="btn btn-primary col col-lg-2 mt-4"
          >
            {createUserByUsernameMutation.isLoading && (
              <Spinner classs="me-2" />
            )}
            {translatedFields.operate.save}
          </button>
        </form>
      </div>
    </div>
  );
};

const Phone = ({
  getJsEncrypt,
  translatedFields,
}: {
  getJsEncrypt: () => Promise<JSEncrypt>;
  translatedFields: PrefixedTTranslatedFields<'userAdminPage'>;
}) => {
  const [differenceData, setDifferenceData] = useState<IDifference[]>([]);
  const { show } = useToast();
  const [form, setForm] = useState({
    phone: '',
  });
  const [isDisabledSave, setIsDisabledSave] = useState(true);

  const createUserByPhoneMutation = useMutation(createUserByPhone);

  const queryAccountPublicKeyQuery = useQuery(
    ['/key', '/public', '/account'],
    async (context) => {
      return (await queryAccountPublicKey(context)) as string;
    },
    {
      enabled: false,
    },
  );

  useEffect(() => {
    const diffData = diff(
      {
        phone: '',
      },
      form,
    );
    setDifferenceData(diffData);
    setIsDisabledSave(diffData.length === 0);
  }, [form]);

  async function onSubmit(e: FormEvent<HTMLFormElement>) {
    try {
      e.preventDefault();
      e.stopPropagation();

      checkForm();
      const data = getDiffData(differenceData);
      const encryptedAccount = await getEncryptedAccount(data.phone);
      await createUserByPhoneMutation.mutateAsync({
        data: {
          ...data,
          phone: encryptedAccount,
        },
      });

      show({
        type: 'SUCCESS',
        message: translatedFields.operate.saveCompleted,
      });
    } catch (e) {
      createUserByPhoneMutation.reset();
      show({
        type: 'DANGER',
        message: e,
      });
    } finally {
      setForm(clearFormData(form));
      setDifferenceData([]);
    }
  }

  async function getEncryptedAccount(account: string): Promise<string> {
    const jsEncrypt = await getJsEncrypt();
    const result = await queryAccountPublicKeyQuery.refetch({
      throwOnError: true,
    });
    const publicKey = result.data;
    if (!publicKey) {
      throw translatedFields.tip.encryptDataFailed;
    }

    jsEncrypt.setPublicKey(publicKey);
    const encryptedData = jsEncrypt.encrypt(account);
    if (!encryptedData) {
      throw translatedFields.tip.encryptDataFailed;
    }

    return encryptedData;
  }

  function checkForm() {
    const { phone } = form;

    if (!phone) {
      throw translatedFields.tip.phoneRequired;
    }

    if (!isPhone(phone)) {
      throw translatedFields.tip.phoneInvalidFormat;
    }
  }

  function onChange(e: ChangeEvent<HTMLInputElement>) {
    const name = e.target.name;
    const value = e.target.value;
    setForm({ ...form, [name]: value });
  }

  return (
    <div className="card">
      <div className="card-header bg-transparent text-secondary">
        {translatedFields.properties.usePhone}
      </div>
      <div className="card-body vstack gap-4">
        <form onSubmit={onSubmit} className="vstack gap-4">
          <div>
            <label className="form-label">
              {translatedFields.properties.phone}
            </label>
            <input
              type="tel"
              name="phone"
              value={form.phone}
              onChange={onChange}
              className="form-control"
              aria-describedby="phone"
            />
          </div>

          <button
            type="submit"
            disabled={createUserByPhoneMutation.isLoading || isDisabledSave}
            className="btn btn-primary col col-lg-2 mt-4"
          >
            {createUserByPhoneMutation.isLoading && <Spinner classs="me-2" />}
            {translatedFields.operate.save}
          </button>
        </form>
      </div>
    </div>
  );
};

const Email = ({
  getJsEncrypt,
  translatedFields,
}: {
  getJsEncrypt: () => Promise<JSEncrypt>;
  translatedFields: PrefixedTTranslatedFields<'userAdminPage'>;
}) => {
  const [differenceData, setDifferenceData] = useState<IDifference[]>([]);
  const { show } = useToast();
  const [form, setForm] = useState({
    email: '',
  });
  const [isDisabledSave, setIsDisabledSave] = useState(true);

  const createUserByEmailMutation = useMutation(createUserByEmail);

  const queryAccountPublicKeyQuery = useQuery(
    ['/key', '/public', '/account'],
    async (context) => {
      return (await queryAccountPublicKey(context)) as string;
    },
    {
      enabled: false,
    },
  );

  useEffect(() => {
    const diffData = diff(
      {
        email: '',
      },
      form,
    );
    setDifferenceData(diffData);
    setIsDisabledSave(diffData.length === 0);
  }, [form]);

  async function onSubmit(e: FormEvent<HTMLFormElement>) {
    try {
      e.preventDefault();
      e.stopPropagation();

      checkForm();
      const data = getDiffData(differenceData);
      const encryptedAccount = await getEncryptedAccount(data.email);
      await createUserByEmailMutation.mutateAsync({
        data: {
          ...data,
          email: encryptedAccount,
        },
      });

      show({
        type: 'SUCCESS',
        message: translatedFields.operate.saveCompleted,
      });
    } catch (e) {
      createUserByEmailMutation.reset();
      show({
        type: 'DANGER',
        message: e,
      });
    } finally {
      setForm(clearFormData(form));
      setDifferenceData([]);
    }
  }

  async function getEncryptedAccount(account: string): Promise<string> {
    const jsEncrypt = await getJsEncrypt();
    const result = await queryAccountPublicKeyQuery.refetch({
      throwOnError: true,
    });
    const publicKey = result.data;
    if (!publicKey) {
      throw translatedFields.tip.encryptDataFailed;
    }

    jsEncrypt.setPublicKey(publicKey);
    const encryptedData = jsEncrypt.encrypt(account);
    if (!encryptedData) {
      throw translatedFields.tip.encryptDataFailed;
    }

    return encryptedData;
  }

  function checkForm() {
    const { email } = form;

    if (!email) {
      throw translatedFields.tip.emailRequired;
    }

    if (!email.includes('.') || !email.includes('@')) {
      throw translatedFields.tip.emailInvalidFormat;
    }
  }

  function onChange(e: ChangeEvent<HTMLInputElement>) {
    const name = e.target.name;
    const value = e.target.value;
    setForm({ ...form, [name]: value });
  }

  return (
    <div className="card">
      <div className="card-header bg-transparent text-secondary">
        {translatedFields.properties.useEmail}
      </div>
      <div className="card-body vstack gap-4">
        <form onSubmit={onSubmit} className="vstack gap-4">
          <div>
            <label className="form-label">
              {translatedFields.properties.email}
            </label>
            <input
              type="email"
              name="email"
              value={form.email}
              onChange={onChange}
              className="form-control"
              aria-describedby="email"
            />
          </div>

          <button
            type="submit"
            disabled={createUserByEmailMutation.isLoading || isDisabledSave}
            className="btn btn-primary col col-lg-2 mt-4"
          >
            {createUserByEmailMutation.isLoading && <Spinner classs="me-2" />}
            {translatedFields.operate.save}
          </button>
        </form>
      </div>
    </div>
  );
};
